using Google.Protobuf;
using Message;
using NativeWebSocket;
using System;
using UnityEngine;

namespace LFramework 
{
    public class WebSocketManager
    {
        [Header("心跳间隔")]
        public int HeartbeatInterval = 10;

        [Header("最大重连次数")]
        private int m_ConnectMaxCount = 3;

        /// <summary>
        /// 上次心跳时间
        /// </summary>
        private float m_PrevHeartbeatTime = 0;

        /// <summary>
        /// PING值(毫秒)
        /// </summary>
        public int PingValue;

        /// <summary>
        /// 游戏服务器的时间
        /// </summary>
        public long LastServerTime;

        /// <summary>
        /// 连接完成
        /// </summary>
        private Action<bool> m_OnConnectComplete;

        /// <summary>
        /// 发送用的MS
        /// </summary>
        private MMO_MemoryStream m_WebSocketSendMS = new MMO_MemoryStream();

        /// <summary>
        /// 接收用的MS
        /// </summary>
        private MMO_MemoryStream m_SocketReceiveMS = new MMO_MemoryStream();

        /// <summary>
        /// 当前重连次数
        /// </summary>
        private int m_CurrConnectCount = 0;

        /// <summary>
        /// 是否重连
        /// </summary>
        private bool m_IsReconnect = false;

        private string m_Ip;
        private int m_Port;
        private WebSocket m_WebSocket;

        public void Init() 
        {
            GameEntry.Event.SocketEvent.AddEventListener(ProtoId.SHeartbeat1, ReceiveHeartbeatMsg);
        }

        public void Dispoase() 
        {
            GameEntry.Event.SocketEvent.RemoveEventListener(ProtoId.SHeartbeat1, ReceiveHeartbeatMsg);

            if (m_WebSocket == null)
                return;

            CloseConnect();
            RemoveListener();
        }

        /// <summary>
        /// 连接主Socket
        /// </summary>
        /// <param name="ip"></param>
        /// <param name="port"></param>
        public void Connect(string ip, int port, Action<bool> onConnectComplete)
        {
            m_Ip = ip;
            m_Port = port;
            m_WebSocket = new WebSocket(string.Format("{0}:{1}", ip, port));
            AddListener();

            m_OnConnectComplete = onConnectComplete;

            StartConnect();
        }

        private void AddListener() 
        {
            m_WebSocket.OnOpen += Socket_OnOpen;
            m_WebSocket.OnError += Socket_OnError;
            m_WebSocket.OnClose += Socket_OnClose;
            m_WebSocket.OnMessage += Socket_OnMessage;
        }

        private void RemoveListener()
        {
            m_WebSocket.OnOpen -= Socket_OnOpen;
            m_WebSocket.OnError -= Socket_OnError;
            m_WebSocket.OnClose -= Socket_OnClose;
            m_WebSocket.OnMessage -= Socket_OnMessage;
        }

        private async void CloseConnect() 
        {
            await m_WebSocket.Close();
        }

        private async void StartConnect() 
        {
            await m_WebSocket.Connect();
        }

        private void Socket_OnOpen()
        {
            GameEntry.Log("Connection open!");

            if (m_IsReconnect)
            {
                m_IsReconnect = false;
                m_CurrConnectCount = 0;
                //重连成功 , 发送 重连事件      
            }
            else 
            {
                m_OnConnectComplete?.Invoke(true);
                m_OnConnectComplete = null;
            }
        }

        private void Socket_OnError(string errorMsg)
        {
            GameEntry.Log("Error! " + errorMsg);
            m_IsReconnect = true;

            if (m_CurrConnectCount < m_ConnectMaxCount)
            {
                Reconnect();
                m_CurrConnectCount++;
            }
            else 
            {
                //弹出检查网络 弹窗
            }
        }

        private void Socket_OnClose(WebSocketCloseCode closeCode)
        {
            GameEntry.Log("Connection closed!");
        }

        private void Socket_OnMessage(byte[] buffer)
        {
            MMO_MemoryStream ms = m_SocketReceiveMS;
            ms.SetLength(0);
            ms.Write(buffer, 0, buffer.Length);
            ms.Position = 0;

            //协议编号
            ushort protoId = ms.ReadUShort();
            byte[] protoData = new byte[buffer.Length - 2];
            Array.Copy(buffer, 4, protoData, 0, protoData.Length);
            GameEntry.Event.SocketEvent.Dispatch(protoId, protoData);
        }

        private void Reconnect() 
        {
            RemoveListener();
            Connect(m_Ip,m_Port,null);
        }

        public void OnUpdate()
        {
            if (m_WebSocket == null)
                return;

            SendHeartbeatMsg();

#if !UNITY_WEBGL || UNITY_EDITOR
            m_WebSocket.DispatchMessageQueue();
#endif
        }

        public void SendMsg(ushort protoId,IMessage msg) 
        {
            MMO_MemoryStream ms = this.m_WebSocketSendMS;
            ms.SetLength(0);

            byte[] buffer = msg.ToByteArray();
            ms.WriteUShort(protoId);
            ms.Write(buffer, 0, buffer.Length);

            byte[] retBuffer = ms.ToArray();
            m_WebSocket.Send(retBuffer);
        }

        /// <summary>
        /// 发送心跳消息
        /// </summary>
        /// <param name="buffer"></param>
        public void SendHeartbeatMsg()
        {
            if (m_WebSocket.State == NativeWebSocket.WebSocketState.Open)
            {
                if (Time.realtimeSinceStartup > m_PrevHeartbeatTime + HeartbeatInterval)
                {
                    //发送心跳
                    m_PrevHeartbeatTime = Time.realtimeSinceStartup;

                    CHeartbeat0 proto = new CHeartbeat0();
                    proto.Time = DateTime.UtcNow.Ticks;
                    proto.Ping = PingValue;

                    SendMsg(ProtoId.CHeartbeat0, proto);
                }
            }
        }

        /// <summary>
        /// 接收心跳消息
        /// </summary>
        /// <param name="buffer"></param>
        public void ReceiveHeartbeatMsg(byte[] buffer)
        {
            SHeartbeat1 proto = SHeartbeat1.Parser.ParseFrom(buffer);
            PingValue = (int)((DateTime.UtcNow.Ticks - proto.Time) * 0.5f / 10000) + proto.ToGameServerPing;
            LastServerTime = proto.ServerTime;
        }
    }
}

